/*
	Copyright (C) 2010 Salil Bhagurkar

	This file is part of k3

	k3 is free software: you can redistribute it and/or modify
	it under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	k3 is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public License
	along with k3.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <shared.h>

/**
 * Buffer management
 * A prefix of na indicates that there are no allocations that the
 * function performs
 */

/**
 * Write a buffer from a source buffer at given offset and
 * len bytes
 */
int buffer_write(struct buffer *buf, struct buffer *src, u32 offset, u32 len)
{
	dprint(D_FREQ, "", "buflen = %u, srclen = %u", buf->len, src->len);
	D_CHECK_NULL(buf, "buf");
	D_CHECK_NULL(src, "src");
	//Calculate the length to which the write will be performed
	u32 write_len = offset + len;
	if(write_len > buf->len) {
		buf->len = write_len;
	}
	if(buf->len > buf->alloc_len) {
		buf->alloc_len = buf->len;
		if(!(buf->data)) {
			dprint(D_FREQ, "", "buffer alloc to %u", buf->alloc_len);
			buf->data = malloc_get(buf->alloc_len, "buffer");
		} else {
			dprint(D_FREQ, "", "buffer resize to %u", buf->alloc_len);
			buf->data = malloc_resize(buf->data, buf->alloc_len);
		}
	}
	memcpy(buf->data + offset, src->data, len);
	return 0;
}

int buffer_write_data(struct buffer *buf, void *data, u32 offset, u32 len)
{
    struct buffer src = {.data = data, .len = len, .alloc_len = len};
	return buffer_write(buf, &src, offset, len);
}

int buffer_read(struct buffer *buf, struct buffer *dst, u32 offset, u32 len)
{
	dprint(D_FREQ, "", "buflen = %u, dstlen = %u", buf->len, dst->len);
	D_CHECK_NULL(buf, "buf");
	if(offset >= buf->len || (offset + len) > buf->len) {
		dprint(D_ALERT, "", "out of bound: %x ", buf);
		return EPBOUND;
	}
	buffer_write(dst, buf, offset, len);
	return 0;
}

int buffer_read_data(struct buffer *buf, void *data, u32 offset, u32 len)
{
	struct buffer src = {.data = data, .len = len, .alloc_len = len};
	return buffer_read(buf, &src, offset, len);
}

struct buffer *buffer_create(u32 size)
{
	dprint(D_FREQ, "", "size = %u", size);
	struct buffer *buf = malloc_get(sizeof(struct buffer), "buffer");
	buf->alloc_len = buf->len = size;
	if(size)
		buf->data = malloc_get(buf->alloc_len, "buffer");
	else
		buf->data = NULL;
	return buf;
}

void buffer_na_init(struct buffer *buf, void *data, u32 len, u32 alloc_len)
{
	dprint(D_FREQ, "", "data = %x, len = %u", data, len);
	buf->data = data;
	buf->len = len;
	buf->alloc_len = alloc_len;
}

struct buffer *buffer_from_string(char *str)
{
	u32 len;
	len = strlen(str);
	struct buffer *buf = buffer_create(len);
	memcpy(buf->data, str, len);
	return buf;
}

char *buffer_to_string(struct buffer *buf)
{
	char *to_string = (char *)malloc_get(buf->len + 1, "buffer");
	memcpy(to_string, buf->data, buf->len);
	to_string[buf->len] = '\0';
	return to_string;
}

struct buffer *buffer_duplicate(struct buffer *buf)
{
	struct buffer *new_buf = buffer_create(buf->len);
	buffer_write(new_buf, buf, 0, buf->len);
	return new_buf;
}

int buffer_free(struct buffer *buf)
{
	dprint(D_FREQ, "", "len = %u", buf->len);
	int err;
	if(buf->data)
		err = malloc_free(buf->data);
	err = malloc_free(buf);
	return err;
}

int buffer_compare(struct buffer *buf, struct buffer *buf1)
{
	if(buf->len != buf1->len)
		return 0;
	return memcmp(buf->data, buf1->data, buf->len);
}

int buffer_append(struct buffer *buf, struct buffer *buf1)
{
	return buffer_write(buf, buf1, buf->len, buf1->len);
}

int buffer_append_data(struct buffer *buf, void *data, u32 len)
{
	return buffer_write_data(buf, data, buf->len, len);
}
